欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

图像(层)正常混合模式详解(下)

程序员文章站 2022-10-17 22:14:11
在一文中开始时说过,图像的合成操作包括图像显示、图像拷贝、图像拼接以及的图层拼合叠加等,本文在基础上谈谈图像拼接和图像显示。     图像拼接比较简...

在一文中开始时说过,图像的合成操作包括图像显示、图像拷贝、图像拼接以及的图层拼合叠加等,本文在基础上谈谈图像拼接和图像显示。

    图像拼接比较简单,只要在图像正常混合函数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对象。

   水平有限,错误在所难免,欢迎指正和指导。邮箱地址:

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