## 图像（层）正常混合模式详解（下） 转

ldh2015

http://www.2cto.com/kf/201201/116377.html一文中开始时说过，图像的合成操作包括图像显示、图像拷贝、图像拼接以及的图层拼合叠加等，本文在http://www.2cto.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.biBitCount = (data->PixelFormat >> 8) & 0xff;

//---------------------------------------------------------------------------

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)
{
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);
// 如果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.biBitCount = (data->PixelFormat >> 8) & 0xff;
}
//---------------------------------------------------------------------------

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)
{
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);
// 如果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.2cto.com/kf/201201/116377.html中的例子运行效果，其截图可参见上面的贴图。显示速度看起来也不会比GDI+的Graphics对象慢（没测试），但如果将http://www.2cto.com/kf/201201/116377.html中的几个混合子函数进行一些优化，其显示速度肯定超过会GDI+的Graphics对象。

水平有限，错误在所难免，欢迎指正和指导。邮箱地址：maozefa@hotmail.com

摘自 闲人阿发伯的业余编程心得

### ldh2015

PS的混合模式

NiceBlueChai
2018/02/18
0
0
Android项目刮刮奖详解（二）

Android项目刮刮奖详解（一） 前言 上期我们简单地实现了一个画板的功能，用户可以在上面乱写乱画，其实，刮刮奖也是如此，用户刮奖的时候也是乱写乱画的。 刮刮奖原理 一共有两层画布，底层...

Stars-one
2018/08/20
0
0
iOS开发CoreGraphics核心图形框架之七——图像处理

iOS开发CoreGraphics核心图形框架之七——图像处理 一、引言 位图图像数据实际上一个像素阵列，其中每个像素代表了图像中的一个点。位图实际上只支持矩形区域的渲染，但是使用透明技术可以实...

2016/11/28
464
0
15种CSS混合模式让图片产生令人惊艳的效果

2014/07/11
8.3K
1
Qt Quick里的图形效果——混合（Blend）

Blend 元素用指定的模式混合两个 Item 。在我们使用 QPainter 绘图时，支持 Composition Modes ，Blend 干的事儿与此类似。 使用 Blend 需要： snippetid="579325" snippetfilename="blog20......

foruok
2015/01/14
0
0

19分钟前
2
0

5
0
PHP常用经典算法实现

<? //-------------------- // 基本数据结构算法 //-------------------- //二分查找（数组里查找某个元素） function bin_sch(\$array, \$low, \$high, \$k){ if ( \$low <= \$high){ \$mid = int......

5
0
GIL 已经被杀死了么？

6
0
git commit message form