VC数字图像处理编程讲座之四

来源:互联网 发布:java 并发 编辑:程序博客网 时间:2024/06/07 08:35

本文章地址:http://tech.163.com/05/1101/17/21G64SVD0009159F.html

   [导读]在这一讲中作者进一步深入讲解了图像特效的显示技术......

BMP图像显示的特效操作

  上期讲座中我们主要讲述了BMP图像数据的存取、图像的显示和调色板的操作等内容,在上面的学习基础上,我们可以进一步深化,学习并掌握图像特效显示技术。有了这种技术,可以用来在今后的项目开发中美化我们的软件界面,提高软件的视觉效果。在如今的商业软件中,几乎每一幅图像的显示都采用了图像特效显示,例如读者比较熟悉的Windows的屏幕保护程序就采用了各种各样的图像特效显示,使人感到眼花缭乱和耳目一新。专业图像处理软件更是提供了丰富的显示方式供用户使用,可以方便的在程序中实现图像的特效显示,如PhotoShop 、Authorware等。本节主要介绍如何实现图像的浮雕、雕刻、百页窗、旋转、扫描、栅条、马赛克、和渐显渐隐显示等效果。通过这期讲座的学习,读者朋友们也可以自己动手制作拥有特效显示效果的软件了。

     图像的显示我们讲过主要有BitBlt()、SetDIBitsToDevice()和StretchDIBits()等函数。需要读者注意的是,在特效显示时,并不是每个显示函数都适宜,BitBlt()函数主要是用来显示设备无关位图(DDB),后两个函数用来显示设备无关位图(DIB)。由于我们讲座里处理的是设备无关位图,所以我们主要关心的是后两个函数的应用,其中SetDIBitsToDevice()使用起来较死板,远不如StretchDIBits()用的灵活,并且对大多数的特效显示无能为力,所以为了实现图像的特效显示效果,需要使用StretchDIBits()函数来显示图像,具体什么原因,我想可能是微软在实现这些函数时使用的方法不同吧。这些函数如何使用,各个参数的含义,可以参考微软的MSDN。

   实现图像的特殊效果的显示的基本思路是要么是操作图像的像素,要么是对图像分块按一定的方向或次序,分阶段的显示或擦除对应的图像块。对于第二种显示的思路,其中的要点是:1.划分图像块;2.确定图像块的操作次序;3.显示或清除对应的图像块;4.在两个连续显示的图像块之间插入一个固定的延迟。其中图像块的划分决定了图像的显示方式,图像块的显示顺序决定了显示的方向和细分的依据。不同的效果决定了不同的分块方法和显示次序,我们将在后面的各种特效显示中介绍如何分块和决定次序。为了使图像的显示过程明显的表现出来,实现显示的特效,就需要在图像块的依此显示中插入固定的延迟。也许读者朋友会想到利用sleep()函数或用Settime()来实现延迟,由于Windows是个基于消息的多任务操作系统,这些方法所产生的延迟时间对于图像的显示来说是不精确的,为了实现与机器无关的更精确的时间延迟,可以采用timeGetTime()函数来产生微秒级的延迟。使用这个函数时为了编译不产生错误,要在连接设置中引入“Winmm.lib”库,并要包含头文件“Mmsystem.h”。这里我们首先给出一个延迟函数,它用来实现固定时间的延迟:

void DelayTime(DWORD time)
{
DWORD BeginTime ,EndTime;
BeginTime=timeGetTime();//得到当前的系统时间、单位为微秒;
do
{
EndTime=TimeGetTime();//再次得到当前的系统时间;
}
while((EndTime-BeginTime) <time)//判断延迟时间是否已经结束;
一、操作位图的像素实现显示的特效

  我们首先介绍直接操作图像中的像素的灰度值来实现图像显示的特效、这里我们主要介绍如何实现图像的浮雕和雕刻效果。经常看电视的朋友们不知注意到没有,有些电视连续剧在每集片头或片尾部分都有显示一些特殊效果的图像,比如前一阵子中央一套放的《长征》和《康熙王朝》,这些特效称为"图像的浮雕效果"和"图像的雕刻效果",经过这些特效处理后的图像增强了观众们的视觉效果,它们看上去仿佛是使用3D技术作的,这也许就是为什么这种技术那么流行的原因吧。其实,我们完全可以用一些简单的数字图像处理算法来实现这些看似复杂高深的显示效果。下面以一个标准的Lena灰度图像为原图,给出了处理后的效果图,同时给出了VC开发平台上的部分实现源代码。

   1."浮雕"图像

  "浮雕"图象效果是指图像的前景前向凸出背景。所谓的"浮雕"概念是指标绘图像上的一个像素和它左上方的那个像素之间差值的一种处理过程,为了使图像保持一定的亮度并呈现灰色,我在处理过程中为这个差值加了一个数值为128的常量。需要读者注意的是,当设置一个像素值的时候,它和它左上方的像素都要被用到,为了避免用到已经设置过的像素,应该从图像的右下方的像素开始处理,下面是实现的源代码:

void CDibView::OnFDImage() //产生"浮雕"效果图函数
{
  HANDLE data1handle;//用来存放图像数据的句柄;
  LPBITMAPINFOHEADER lpBi;//图像的信息头结构;
  CDibDoc *pDoc=GetDocument();//得到文挡指针;
  HDIB hdib;//用来存放图像数据的句柄;
  unsigned char *pData;//指向原始图像数据的指针;
  unsigned char *data;//指向处理后图像数据的指针;
  hdib=pDoc->m_hDIB;//拷贝存放已经读取的图像文件数据句柄;
  lpBi=(LPBITMAPINFOHEADER)GlobalLock((HGLOBAL)hdib);//获取图像信息头
pData=(unsigned char*)FindDIBBits((LPSTR)lpBi);
//FindDIBBits是我定义的一个函数、根据图像的结构得到位图的灰度值数据、
pDoc->SetModifiedFlag(TRUE);
//设置文档修改标志为“真”、为后续的修改存盘作准备;
data1handle=GlobalAlloc(GMEM_SHARE,WIDTHBYTES(lpBi->biWidth*8)*lpBi->biHeight); //声明一个缓冲区用来暂存处理后的图像数据;
data=(unsigned char*)GlobalLock((HGLOBAL)data1handle);//得到该缓冲区的指针;
AfxGetApp()->BeginWaitCursor();
int i,j,buf;
for( i=lpBi->biHeight; i>=2; i--)//从图像右下角开始对图像的各个像素进行“浮雕”处理;
   for( j=lpBi->biWidth; j>=2; j--)
   {
//浮雕处理
buf=*(pData+(lpBi->biHeight-i)*WIDTHBYTES(lpBi->biWidth*8)+j)-*(pData+(lpBi->biHeight-i+1)*WIDTHBYTES(lpBi->biWidth*8)+j-1)+128;
if(buf>255) buf=255;
if(buf<0)buf=0; *(data+(lpBi->biHeight-i)*WIDTHBYTES(lpBi->biWidth*8)+j)=(BYTE)buf;
}
for( j=0; jbiHeight; j++)
for( i=0; ibiWidth; i++)
//重新写回原始图像的数据缓冲区;
*(pData+i*WIDTHBYTES(lpBi->biWidth*8)+j)=*(data+i*WIDTHBYTES(lpBi->biWidth*8)+j);    AfxGetApp()->EndWaitCursor();
pDoc->m_hDIB =hdib//将处理过的图像数据写回pDoc中的图像缓冲区;
GlobalUnlock((HGLOBAL)hdib);//解锁、释放缓冲区;
GlobalUnlock((HGLOBAL)data1handle);
GlobalFree((HGLOBAL)hdib);
GlobalFree((HGLOBAL)data1handle);
Invalidate(TRUE);//显示图像
}

2."雕刻"图像

   上面讲述了通过求一个像素和它左上方像素之间的差值并加上一个常数的方法生成"浮雕"效果的灰度图像,"雕刻"图像与之相反,它是通过取一个像素和它右下方的像素之间的差值并加上一个常数,这里我也取128,经过这样处理,就可以得到"雕刻"图像,这时候图像的前景凹陷进背景之中。同样需要读者注意的是为了避免重复使用处理过的图像像素,处理图像时要从图像的左上方的像素开始处理。实现代码如下:

void CDibView::OnDKImage()
{
  // TODO: Add your command handler code here
  HANDLE data1handle;//这里的内部变量与前面的含义一致、这里不再赘述;
  LPBITMAPINFOHEADER lpBi;
  CDibDoc *pDoc=GetDocument();
  HDIB hdib;
  unsigned char *pData;
  unsigned char *data;
  hdib=pDoc->m_hDIB;//拷贝图像数据的句柄;
  lpBi=(LPBITMAPINFOHEADER)GlobalLock((HGLOBAL)hdib);
  pData=(unsigned char*)FindDIBBits((LPSTR)lpBi);
  pDoc->SetModifiedFlag(TRUE);
  data1handle=GlobalAlloc(GMEM_SHARE,WIDTHBYTES(lpBi->biWidth*8)*lpBi->biHeight);//申请缓冲区;
  data=(unsigned char*)GlobalLock((HGLOBAL)data1handle);//得到新的缓冲去的指针; AfxGetApp()->BeginWaitCursor();
  int i,j,buf;
  for( i=0;i<=lpBi->biHeight-2; i++)//对图像的各个像素循环进行"雕刻"处理;
   for( j=0;j<=lpBi->biWidth-2; j++)
  {
    buf=*(pData+(lpBi->biHeight-i)*WIDTHBYTES(lpBi->biWidth*8)+j)-*(pData+(lpBi->biHeight-i-1)*WIDTHBYTES(lpBi->biWidth*8)+j+1)+128;//“雕刻”处理;
  if(buf>255) buf=255;
  if(buf<0)buf=0;
  *(data+(lpBi->biHeight-i)*WIDTHBYTES(lpBi->biWidth*8)+j)=(BYTE)buf;
  }
  for( j=0; jbiHeight; j++)
  for( i=0; ibiWidth; i++)  //重新将处理后的图像数据写入原始的图像缓冲区内;  *(pData+i*WIDTHBYTES(lpBi->biWidth*8)+j)=*(data+i*WIDTHBYTES(lpBi->biWidth*8)+j);
pDoc->m_hDIB =hdib//将处理过的图像数据写回pDoc中的图像缓冲区;
GlobalUnlock((HGLOBAL)hdib);//解锁、释放缓冲区;
GlobalUnlock((HGLOBAL)data1handle);
GlobalFree((HGLOBAL)hdib);
GlobalFree((HGLOBAL)data1handle);
Invalidate(TRUE);//显示图像
}

  3.图像的旋转

  根据图像像素的位置来调节该位置的灰度可以实现许多显示的特效,例如图像的镜像、翻转等。灰度图像旋转就是根据这一个思想实现的,它是指把定义的图像绕某一点以逆时针或顺时针方向旋转一定的角度,通常是指绕图像的中心以逆时针方向旋转。首先根据旋转的角度、图像对角线的长度计算旋转后的图像的最大宽度、高度,根据旋转后图象最大的宽度、高度生成新的缓冲区,假设图像的左上角为(left, top),右下角为(right, bottom),则图像上任意点(x, y)绕其中心(xcenter, ycenter)逆时针旋转angle角度后,新的坐标位置(x1, y1)的计算公式为:

  xcenter = (width+1)/2+left;
   ycenter = (height+1)/2+top;
   x1 = (x-xcenter) cosθ - (y - ycenter) sinθ+xcenter;
   y1 = (x-xcenter) sinθ+ (y- ycenter) cosθ+ ycenter;

二、图像的分块显示和清除

  1. 图像的扫描显示和清除

   扫描显示图像是最基本的特效显示方法,它表现为图像一行行(或一列列)地显示出来或从屏幕上清除掉,有种大戏院种的拉幕效果。根据扫描的方向的不同,可以分为上、下、左、右、水平平分和垂直平分等六种扫描。这里以向下移动为例,分别介绍显示和清除的实现。其余的扫描效果可以依次类推。向下扫描显示的实现方法是:从图像的底部开始将图像一行一行的复制到目标区域的顶部。每复制一行后,复制的行数便要增加一行,并加上一些延迟;向下移动清除的实现方法是图像向下移动显示,并在显示区域的上部画不断增高的矩形。

   1)扫描显示的代码:

CdibView::OnImageDownScan()
{
CDibDoc *pDoc=GetDocument();
HDIB hdib;
CClientDC pDC(this);
hdib=pDoc->m_hDIB;//获取图像数据句柄;
BITMAPINFOHEADER *lpDIBHdr;//位图信息头结构指针;
BYTE *lpDIBBits;//指向位图像素灰度值的指针;
HDC hDC=pDC.GetSafeHdc();//获取当前设备上下文的句柄;
lpDIBHdr=( BITMAPINFOHEADER *)GlobalLock(hdib);//得到图像的位图头信息;
lpDIBBits=(BYTE*)lpDIBHdr+sizeof(BITMAPINFOHEADER)+256*sizeof(RGBQUAD);//获取指向图像像素值;
SetStretchBltMode(hDC,COLORONCOLOR);
//显示图像;
for(int i=0;i<lpDIBHdr->biHeight;i++)
{ //每次循环显示图象的“0”到“i”行数据;
SetDIBitsToDevice (hDC,0,0,lpDIBHdr->biWidth, lpDIBHdr->biHeight,
0, 0,0, i,
lpDIBBits,(LPBITMAPINFO)lpDIBHdr,
DIB_RGB_COLORS
);
DelayTime(50);//延迟;
}
GlobalUnlock(hdib);
return;
}

  2)清除代码:

…………………………………//由于篇幅的限制,省略了与上面的相同代码
Cbrush brush(crWhite);//定义一个“白色”的刷子;
Cbrush *oldbrush=pDC->SelectObject(&brush);
for(int i=0;i < lpDIBHdr->biHeight ;i++)
{//每次循环将目标区域中的“0”到“i”行刷成“白色”;
pDC->Rectangle(0,0,lpDIBHdr->biWidth,lpDIBHdr->biHeight);
DelayTime(50);
}
…………………………………

  2. 百页窗效果

  所谓百页窗显示效果,就如同关闭和开启百页窗一样,图像被分为一条条或一列列地分别显示或清除掉,根据显示时以行或列为单位可以将该效果分为垂直或水平两种方式。以垂直百页窗为例来说明如何实现这种特效显示。实现垂直百页窗显示时,需要将图像垂直等分为n部分由上向下扫描显示,其中每一部分包括m个条、这个n可以根据具体应用时的需要来决定、m既为图像的高度除n。扫描显示时,依照差值进行扫描显示,即第k次显示k-1、k*m-1、…k*n-1条扫描线。同样,垂直百页窗清除的实现与垂直百页窗的显示相似,不同的是将绘制位图换成画矩形而已。在下面的例子中,我将图像的分成8份。

…………………………………
int m=8;
int n=lpDIBHdr->biHeight/m;//图像的高度能够整除8;
for(int l=1;l<=m;l++)
for(int k=0;k<n;k++)
{ //每次循环依次显示图像中的k-1、k*m-1、…k*n-1行;
StretchDIBits (hDC,0,4*k+l-1,lpDIBHdr->biWidth,1,
0, lpDIBHdr->biHeight-4*k-l+1,lpDIBHdr->biWidth,1,
lpDIBBits,(LPBITMAPINFO)lpDIBHdr,
DIB_RGB_COLORS,
SRCCOPY);//juanlianxiaoguo

DelayTime(50);
}
…………………………………

    3.栅条显示特效

   栅条特效是移动特效的复杂组合,可以分为垂直栅条和水平栅条两类。它的基本思想是将图像分为垂直或水平的的小条,奇数条向上或向左显示/清除,偶数条向下或向右显示/清除。当然也可以规定进行相反的方向显示/清除。下面的代码是实现垂直栅条的例子:

…………………………………
int m=8;
for(int i=0;i<=lpDIBHdr->biHeight;i++)
for(int j=0;j<=lpDIBHdr->biWidth;j+=m)
{//向下显示偶数条;
StretchDIBits (hDC,j,0,m,i,j,lpDIBHdr->biHeight-i,
m,i,
lpDIBBits,(LPBITMAPINFO)lpDIBHdr,
DIB_RGB_COLORS,
SRCCOPY);//juanlianxiaoguo
j=j+m;
//向上显示奇数条;
StretchDIBits (hDC,j,lpDIBHdr->biHeight-i,m,i,j,0,
m,i,
lpDIBBits,(LPBITMAPINFO)lpDIBHdr,
DIB_RGB_COLORS,
SRCCOPY);//
DelayTime(20);
}…………………………………

  4.马赛克效果

  马赛克显示是指图像被分成许多的小块,它们以随机的次序显示出来,直到图像显示完毕。实现马赛克的效果主要解决的问题是如何定义显示随机序列的小方块,这个问题的解决可以在定义过小方块的基础上,用一个数组来记录各个方块的左上角的坐标的位置。显示图像过程中,产生一个随机数来挑选即将显示的小方块,显示后将该方块的位置坐标从数组中剔除。清除过程与之相仿。剔除显示过的方块的位置坐标的方法是将该数组中的最后的一个点的坐标拷贝到当前位置,然后删除数组中的最后点的坐标,经过实现发现这样处理有时显示的图像是不完整的,分析其原因是生成随机数的过程有舍入溢出误差。读者可以采用其它的办法解决这个问题,例如可以生成固定的随机数组或采用一个动态的数组来跟踪未显示的图像方块的坐标等方法。

…………………………………
int m,n;
int RectSize=60;//方块的宽、高尺寸为60个像素;
if(lpDIBHdr->biWidth%RectSize!=0)//得到图像水平方块的个数;
m= lpDIBHdr->biWidth/RectSize+1;
else
m= lpDIBHdr->biWidth/RectSize;
if(lpDIBHdr->biHeight%RectSize!=0)//得到图像垂直方块的个数;
n= lpDIBHdr->biHeight/RectSize+1;
else
n=lpDIBHdr->biHeight/RectSize;
POINT *point=new POINT[n*m];//申请一个数组用来记录各个方块的左上角的坐标;
POINT point1;
for(int a=0;a<m;a++)//将各个方块的左上角的坐标记录到数组中;
for(int b=0;b<n;b++)
{
point1.x=a*RectSize;
point1.y=b*RectSize;
*(point+a*b+b)=point1;
}
//开始随机的显示各个小方块;
double fMax=RAND_MAX;//定义Rand()函数的最大值;
for(int k=m*n-1;k>=0;k--)
{
int c=(int)((double)(m*n)*rand()/fMax);
int mx=point[c].x;
int my=point[c].y;
//显示对应的图像的小块;
StretchDIBits (hDC,mx,my,RectSize,RectSize,
mx,lpDIBHdr->biHeight-my,RectSize,RectSize,
lpDIBBits,(LPBITMAPINFO)lpDIBHdr,
DIB_RGB_COLORS,
SRCCOPY);
point[c].x=point[k].x;
point[c].y=point[k].y;
DelayTime(50);
}
…………………………………

  5.图像的淡入淡出效果

  图像的淡入淡出的显示效果被广泛的应用在多媒体娱乐软件中,是一种特别重要的特效显示方法。淡入就是将显示图像的目标区域由本色逐渐过度的图像中的各个像素点的颜色;淡出就是由显示的图像逐渐过度到目标区域的本色。实现图像的淡入淡出有两种办法:一是均匀的改变图像的调色板中的颜色索引值;另一种方法是改变图像像素的灰度值。第一种方法实现起来比较繁琐,第二种方法就比较简单。下面是我们采用第二种方法实现图像淡入效果的代码:

…………………………………
//申请一个与图像缓冲区相同大小的内存;
hdibcopy=(HDIB)GlobalAlloc(GMEM_SHARE,lpDIBHdr->biWidth*lpDIBHdr->biHeight);
lpbits=(BYTE*)GlobalLock(hdibcopy);
//将缓冲区的数据初始化;
for(int k=0;k<lpDIBHdr->biWidth*lpDIBHdr->biHeight;k++)
{
*(lpbits+k)=(BYTE)255;
}
//显示最初的图像为“白色”
StretchDIBits (hDC,0,0,lpDIBHdr->biWidth,lpDIBHdr->biHeight,0,0,
lpDIBHdr->biWidth,lpDIBHdr->biHeight,
lpbits,(LPBITMAPINFO)lpDIBHdr,
DIB_RGB_COLORS,
SRCCOPY);
//布尔变量end用来标志何时淡入处理结束;
BOOL end=false;
while(!end)
{ int a=0;
for(int k=0;k<lpDIBHdr->biWidth*lpDIBHdr->biHeight;k++)
{
//判断是否待显示的像素的灰度值已经小于原始图像对应点的灰度值,如是则计数;
if(*(lpbits+k)<*(lpDIBBits+k))
a++;
else//否则对应点的灰度值继续减少;
*(lpbits+k)-=(BYTE)10;
}
//显示处理后的图像数据lpbits;
StretchDIBits (hDC,0,0,lpDIBHdr->biWidth,lpDIBHdr->biHeight,0,0,
lpDIBHdr->biWidth,lpDIBHdr->biHeight,
lpbits,(LPBITMAPINFO)lpDIBHdr,
DIB_RGB_COLORS,
SRCCOPY);
//如果所有的点的灰度值的都小于或等于原始图像的像素点的灰度值,则认为图像的淡入处理结束。
if(a==lpDIBHdr->biWidth*lpDIBHdr->biHeight)
end=true; DelayTime(50);
…………………………………

  本文上面的内容介绍了几种图像的特殊显示效果,代码在Windows2000和Visual C++6.0编程环境下编译通过,运行正常,处理达到了预期的效果。读者可以将上面介绍的显示图像的函数和处理思路结合起来,实现更多效果。

原创粉丝点击