在http://www.BkJia.com/kf/201201/116377.html一文中開始時說過,圖像的合成操作包括圖像顯示、圖像拷貝、圖像拼接以及的圖層拼合疊加等,本文在http://www.BkJia.com/kf/201201/116377.html基礎上談談圖像拼接和圖像顯示。
圖像拼接比較簡單,只要在圖像正常混合函數ImageMixer基礎上定位圖像混合坐標就可以了。下面是一個有圖像混合坐標的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 = (INT)data->Width - x;
if (width <= 0) return FALSE;
if (y < 0)
{
height += y;
y = 0;
}
if (y + height > (INT)data->Height)
height = (INT)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);
}
}
//---------------------------------------------------------------------------
// 獲取子圖數據
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 = (INT)data->Width - x;
if (width <= 0) return FALSE;
if (y < 0)
{
height += y;
y = 0;
}
if (y + height > (INT)data->Height)
height = (INT)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);
}
}
//---------------------------------------------------------------------------
上面的代碼中增加了一個獲取子圖像數據結構的函數GetSubBitmapData和一個有圖像混合坐標的重載函數ImageMixer,在重載函數ImageMixer中,調用GetSubBitmapData獲取了一個按圖像混合起始坐標(x,y)計算的目標圖與源圖的交集子圖像數據結構,然後將原圖像混合到目標子圖像上。通過多次這樣的混合操作就可以實現圖像的拼接。
下面是一個使用BCB2007和GDI+實現圖像拼接的例子程序:
void __fastcall TForm1::Button3Click(TObject *Sender)
{
BitmapData data, dst, src;
// 建立新圖像
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);
// 目標圖與源圖混合
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;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button3Click(TObject *Sender)
{
BitmapData data, dst, src;
// 建立新圖像
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);
// 目標圖與源圖混合
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;
}
//---------------------------------------------------------------------------
運行效果截圖如下:
運行效果與《圖像(層)正常混合模式詳解(上)》例子運行效果截圖是一樣的(其實就是同一張圖片),但含義卻是不一樣的:《圖像(層)正常混合模式詳解(上)》例子是分多次在窗口上顯示,而上面例子卻是將源圖和目標圖拼接(多次混合)到一張圖上,然後再顯示。
其實,上面的ImageMixer函數沒有對源圖進行混合坐標定位,也是不太完善的,不過有了GetSubBitmapData函數,對源圖進行坐標定位是很簡單的。
下面再應用ImageMixer函數實現圖像顯示功能,代碼如下:
VOID GetBitmapInfoHeader(CONST BitmapData *data, CONST PBITMAPINFO pbi)
{
pbi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
pbi->bmiHeader.biWidth = data->Width;
pbi->bmiHeader.biHeight = data->Height;
pbi->bmiHeader.biPlanes = 1;
pbi->bmiHeader.biBitCount = (data->PixelFormat >> 8) & 0xff;
pbi->bmiHeader.biCompression = BI_RGB;
}
//---------------------------------------------------------------------------
VOID GetDCImageData(HDC DC, INT x, INT y, BitmapData *data, PBITMAPINFO pbi)
{
HBITMAP bitmap = CreateCompatibleBitmap(DC, data->Width, data->Height);
HDC memDC = CreateCompatibleDC(DC);
HBITMAP saveBitmap = (HBITMAP)SelectObject(memDC, bitmap);
BitBlt(memDC, 0, 0, data->Width, data->Height, DC, x, y, SRCCOPY);
SelectObject(memDC, saveBitmap);
DeleteDC(memDC);
GetDIBits(DC, bitmap, 0, data->Height, data->Scan0, pbi, DIB_RGB_COLORS);
DeleteObject(bitmap);
}
//---------------------------------------------------------------------------
VOID BitBltImageData(HDC DC, INT x, INT y, CONST BitmapData *data, PBITMAPINFO pbi)
{
HBITMAP bitmap = CreateDIBitmap(DC, &pbi->bmiHeader, CBM_INIT, data->Scan0, pbi, DIB_RGB_COLORS);
HDC memDC = CreateCompatibleDC(DC);
HBITMAP saveBitmap = (HBITMAP)SelectObject(memDC, bitmap);
BitBlt(DC, x, y, data->Width, data->Height, memDC, 0, 0, SRCCOPY);
SelectObject(memDC, saveBitmap);
DeleteDC(memDC);
DeleteObject(bitmap);
}
//---------------------------------------------------------------------------
VOID ImageDraw(HDC DC, INT x, INT y, CONST BitmapData *data, float alpha = 1.0f)
{
BITMAPINFO bi;
RECT r;
INT alphaI;
LPVOID scan0;
BitmapData dst, src, tmp;
// 獲取DC可見矩形
if (GetClipBox(DC, &r) <= NULLREGION)
return;
alphaI = (INT)(alpha * 255);
// 如果alpha=1,同時data不含alpha信息,同時data是Windows位圖格式,
// data圖像數據直接傳輸到DC
if (alphaI >= 255 && !(data->Reserved & PixelAlphaFlag) && data->Stride < 0)
{
GetBitmapInfoHeader(data, &bi);
BitBltImageData(DC, x, y, data, &bi);
return;
}
// 調整DC可見矩形左上角坐標
x -= r.left;
y -= r.top;
if (x > 0)
{
r.left += x;
x = 0;
}
if (y > 0)
{
r.top += y;
y = 0;
}
// 計算data傳輸到DC的實際尺寸到圖像數據dst
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;
// 按32位像素格式分配dst掃描線內存
dst.Stride = dst.Width << 2;
dst.Scan0 = scan0 = (LPVOID)new CHAR[dst.Height * dst.Stride];
// 計算data與DC的交集子圖像數據
if (x < 0) x = -x;
if (y < 0) y = -y;
GetSubBitmapData(data, x, y, dst.Width, dst.Height, &src);
GetBitmapInfoHeader(&src, &bi);
// 如果alpha<1,或者data含Alpha信息,獲取DC原圖形到dst
if (alphaI < 255 || (data->Reserved & PixelAlphaFlag));
GetDCImageData(DC, r.left, r.top, &dst, &bi);
// dst掃描線內存轉換成Windows位圖格式
dst.Scan0 = (LPBYTE)scan0 + (dst.Height - 1) * dst.Stride;
dst.Stride = -dst.Stride;
// 圖像混合
ImageMixer(&dst, &src, alphaI);
// 還原dst掃描線內存格式後,傳輸到DC
dst.Scan0 = scan0;
BitBltImageData(DC, r.left, r.top, &dst, &bi);
delete[] scan0;
}
//---------------------------------------------------------------------------
VOID GetBitmapInfoHeader(CONST BitmapData *data, CONST PBITMAPINFO pbi)
{
pbi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
pbi->bmiHeader.biWidth = data->Width;
pbi->bmiHeader.biHeight = data->Height;
pbi->bmiHeader.biPlanes = 1;
pbi->bmiHeader.biBitCount = (data->PixelFormat >> 8) & 0xff;
pbi->bmiHeader.biCompression = BI_RGB;
}
//---------------------------------------------------------------------------
VOID GetDCImageData(HDC DC, INT x, INT y, BitmapData *data, PBITMAPINFO pbi)
{
HBITMAP bitmap = CreateCompatibleBitmap(DC, data->Width, data->Height);
HDC memDC = CreateCompatibleDC(DC);
HBITMAP saveBitmap = (HBITMAP)SelectObject(memDC, bitmap);
BitBlt(memDC, 0, 0, data->Width, data->Height, DC, x, y, SRCCOPY);
SelectObject(memDC, saveBitmap);
DeleteDC(memDC);
GetDIBits(DC, bitmap, 0, data->Height, data->Scan0, pbi, DIB_RGB_COLORS);
DeleteObject(bitmap);
}
//---------------------------------------------------------------------------
VOID BitBltImageData(HDC DC, INT x, INT y, CONST BitmapData *data, PBITMAPINFO pbi)
{
HBITMAP bitmap = CreateDIBitmap(DC, &pbi->bmiHeader, CBM_INIT, data->Scan0, pbi, DIB_RGB_COLORS);
HDC memDC = CreateCompatibleDC(DC);
HBITMAP saveBitmap = (HBITMAP)SelectObject(memDC, bitmap);
BitBlt(DC, x, y, data->Width, data->Height, memDC, 0, 0, SRCCOPY);
SelectObject(memDC, saveBitmap);
DeleteDC(memDC);
DeleteObject(bitmap);
}
//---------------------------------------------------------------------------
VOID ImageDraw(HDC DC, INT x, INT y, CONST BitmapData *data, float alpha = 1.0f)
{
BITMAPINFO bi;
RECT r;
INT alphaI;
LPVOID scan0;
BitmapData dst, src, tmp;
// 獲取DC可見矩形
if (GetClipBox(DC, &r) <= NULLREGION)
return;
alphaI = (INT)(alpha * 255);
// 如果alpha=1,同時data不含alpha信息,同時data是Windows位圖格式,
// data圖像數據直接傳輸到DC
if (alphaI >= 255 && !(data->Reserved & PixelAlphaFlag) && data->Stride < 0)
{
GetBitmapInfoHeader(data, &bi);
BitBltImageData(DC, x, y, data, &bi);
return;
}
// 調整DC可見矩形左上角坐標
x -= r.left;
y -= r.top;
if (x > 0)
{
r.left += x;
x = 0;
}
if (y > 0)
{
r.top += y;
y = 0;
}
// 計算data傳輸到DC的實際尺寸到圖像數據dst
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;
// 按32位像素格式分配dst掃描線內存
dst.Stride = dst.Width << 2;
dst.Scan0 = scan0 = (LPVOID)new CHAR[dst.Height * dst.Stride];
// 計算data與DC的交集子圖像數據
if (x < 0) x = -x;
if (y < 0) y = -y;
GetSubBitmapData(data, x, y, dst.Width, dst.Height, &src);
GetBitmapInfoHeader(&src, &bi);
// 如果alpha<1,或者data含Alpha信息,獲取DC原圖形到dst
if (alphaI < 255 || (data->Reserved & PixelAlphaFlag));
GetDCImageData(DC, r.left, r.top, &dst, &bi);
// dst掃描線內存轉換成Windows位圖格式
dst.Scan0 = (LPBYTE)scan0 + (dst.Height - 1) * dst.Stride;
dst.Stride = -dst.Stride;
// 圖像混合
ImageMixer(&dst, &src, alphaI);
// 還原dst掃描線內存格式後,傳輸到DC
dst.Scan0 = scan0;
BitBltImageData(DC, r.left, r.top, &dst, &bi);
delete[] scan0;
}
//---------------------------------------------------------------------------
ImageDraw函數實現了直接顯示BitmapData位圖數據到設備DC。其中的幾個步驟都作了注釋,這裡不再啰嗦,至於其中調用的Windows API,也請參見Windows API大全之類的書籍。
下面將《圖像(層)正常混合模式詳解(上)》中的例子修改一下,將其中的GDI+的Graphics對象顯示位圖,改為上面的ImageDraw函數直接顯示位圖數據結構:
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;
}
//---------------------------------------------------------------------------
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;
}
//---------------------------------------------------------------------------
顯示效果同http://www.BkJia.com/kf/201201/116377.html中的例子運行效果,其截圖可參見上面的貼圖。顯示速度看起來也不會比GDI+的Graphics對象慢(沒測試),但如果將http://www.BkJia.com/kf/201201/116377.html中的幾個混合子函數進行一些優化,其顯示速度肯定超過會GDI+的Graphics對象。
水平有限,錯誤在所難免,歡迎指正和指導。郵箱地址:[email protected]
摘自 閒人阿發伯的業余編程心得