图像(层)正常混合模式详解(下)
在一文中开始时说过,图像的合成操作包括图像显示、图像拷贝、图像拼接以及的图层拼合叠加等,本文在基础上谈谈图像拼接和图像显示。
图像拼接比较简单,只要在图像正常混合函数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;
}
//---------------------------------------------------------------------------
显示效果同中的例子运行效果,其截图可参见上面的贴图。显示速度看起来也不会比gdi+的graphics对象慢(没测试),但如果将中的几个混合子函数进行一些优化,其显示速度肯定超过会gdi+的graphics对象。
水平有限,错误在所难免,欢迎指正和指导。邮箱地址:
摘自 闲人阿发伯的业余心得