文档章节

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

ldh2015
 ldh2015
发布于 2016/04/01 13:39
字数 2320
阅读 8
收藏 0

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

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

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


本文转载自:http://www.2cto.com/kf/201201/116398.html

ldh2015
粉丝 0
博文 29
码字总数 6948
作品 0
深圳
程序员
私信 提问
PS的混合模式

如果你要经常使用Photoshop,那么你需要了解Photoshop一个简单却不容易理解的特性——混合模式。在Photoshop众多酷炫功能中,这是个很容易被人忽略的功能,但是看完这篇文章之后,希望会引起...

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混合模式让图片产生令人惊艳的效果

如果你曾经广泛地使用过图片编辑应用软件(例如,Adobe Photoshop,Pixelmator,GIMP等),那么你可能对混合模式比较熟悉。如同名称中所暗示的,混合模式是指将上层的图像融入下层图像时采用...

欲思
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

没有更多内容

加载失败,请刷新页面

加载更多

我为什么要写微信公众号

埋一颗种子,细心呵护,静待她枝繁叶茂,葱郁参天 V2论坛上有个帖子【做程序员最重要的还是一定要有自己的作品】,作者写道: 能有一个作品和你的名字联系在一起,应当成为在职业生涯前期着意...

运维咖啡吧
19分钟前
2
0
数据库

数据库架构 数据库架构可以分为存储文件系统和程序实例两大块,而程序实例根据不同的功能又可以分为如下小模块。 1550644570798 索引模块 常见的问题有: 为什么要使用索引 什么样的信息能成...

一只小青蛙
今天
5
0
PHP常用经典算法实现

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

半缘修道半缘君丶
昨天
5
0
GIL 已经被杀死了么?

本文原创并首发于公众号【Python猫】,未经授权,请勿转载。 原文地址:https://mp.weixin.qq.com/s/8KvQemz0SWq2hw-2aBPv2Q 花下猫语: Python 中最广为人诟病的一点,大概就是它的 GIL 了。...

豌豆花下猫
昨天
6
0
git commit message form

commit message一般包括3部分:Header、Body、Footer。 <type>(<scope>):<subject>blank line<body>blank line<footer> header是必需的,body、footer可以省略。 header中type、subject......

ninjaFrog
昨天
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部